package com.boc.ewr.util.file;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.boc.ewr.bcimp.entity.CaBalDtl;
import com.boc.ewr.bcimp.entity.EwrBatchuploadInfo;
import com.boc.ewr.bcimp.service.IEwrBatchuploadInfoService;
import com.boc.ewr.util.file.annotation.ImportEnumAnnotation;
import com.boc.ewr.util.file.enumeration.ExportEnum;
import com.boc.ewr.util.file.enumeration.ImportEnum;
import com.xkcoding.http.util.StringUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.SpringContextUtils;
import org.jeewx.api.core.util.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 上传文件数据到指定路径
 */
@Slf4j
@Service
public class TxtImportUtil {


    private static final String REGEX = "\\|"; // 文件分隔符

    private static final String DEFAULTPATH = "/home/bocewr/upFiles"; // 文件分隔符

    @Autowired
    IEwrBatchuploadInfoService infoService;

    /**
     * 根据上传过来的文件传输到指定路径下
     *
     * @param request
     * @param path
     * @return
     * @throws Exception
     */
    public List<String> uploadTxt(HttpServletRequest request, String path) throws Exception {
        log.info("上传文件开始==============");
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
        if (StringUtils.isEmpty(path)) {
            path = DEFAULTPATH;
        }
        List<String> result = new ArrayList<>();
        if (fileMap == null || fileMap.size() == 0) {
            return result;
        }
        long start = System.currentTimeMillis();
        try {
            SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd");
            String dirName = sf.format(new Date());
            String filePath = path + "/" + dirName;//生成的文件路径
            CommonUtils.filterFileName(filePath);
            File fileNew = FileUtils.getFile(filePath);
            if (!fileNew.exists()) {//判断目录是否存在
                fileNew.mkdirs();
            }
//            List<String> showUrls = new ArrayList<>();
            for (Map.Entry<String, MultipartFile> file : fileMap.entrySet()) {
                MultipartFile singleFile = file.getValue();
                log.info("上传单个文件开始;文件名:" + singleFile.getOriginalFilename());
                long localStartTime = System.currentTimeMillis();
                //生成文件名称已uuid命名
//                String uuid = UUID.randomUUID().toString().replaceAll("-","");
                String fileName = singleFile.getOriginalFilename();
                String filePathNew = filePath + "/" + fileName;
                CommonUtils.filterFileName(filePathNew);
                File newFile = FileUtils.getFile(filePathNew);
                singleFile.transferTo(newFile);
                log.info("上传单个文件结束,【" + fileName + "】所耗时间:" + (System.currentTimeMillis() - localStartTime) + "ms");
                String showUrl = filePathNew;
                result.add(showUrl);
            }
//            result.put("showUrls", showUrls);
            log.info("批量上传文件结束==============总耗时:" + (System.currentTimeMillis() - start) + " ms");
        } catch (Exception e) {
            log.error("批量上传到服务器出错！", e);
        }
        return result;
    }

    /**
     * 将导入的Txt文档转化成对应的对象，并上传到数据库中
     *
     * @param showUrls
     * @param businessType
     * @return
     */
    public boolean importTxt(List<String> showUrls, String businessType) throws IOException {
        //根据businessType类型选择对象
        ImportEnum importType = ImportEnum.getByIndex(businessType);
        List<Object> objList = new ArrayList<>();
        boolean tmepFlag = true;//用于设置判断是否需要删除源数据
        String errMsg = ""; //错误信息反显
        long start = System.currentTimeMillis();
        /**
         * 因为文件大小约定了，总量不会超过80M，所以直接指定一个集合先处理总数据，再整体处理插入，避免数据库多线程问题
         * 还有批量插入数据时候，主键ID重复冲突的问题
         */
        for (String filePath : showUrls) {
            log.info("解析文件开始，文件名:" + filePath);
            //读取txt文件流
            BufferedReader bufReader = null;
            InputStreamReader inputStreamReader = null;
            FileInputStream fileInputStream = null;
            try {
                CommonUtils.filterFileName(filePath);
                File file = FileUtils.getFile(filePath);
                fileInputStream =  FileUtils.openInputStream(file);
                inputStreamReader = new InputStreamReader(fileInputStream, "GBK");
                bufReader = new BufferedReader(inputStreamReader);
                String line = "";
                int i = 0;
                //读取每行内容，并插入到数据库中
                while ((line = bufReader.readLine()) != null) {
                    i++;
                    if (i <= 1) {
                        //跳过txt文档里面的表头行
                        continue;
                    }
                    //去除空格和文件最后一位|
                    String sbreplace = line.toString().trim().substring(0, line.length() - 1);
                    if (sbreplace.length() <= 1) {
                        //防止文件最后一行有结束符合，直接跳过
                        continue;
                    }
                    //转换成为对象
                    Object obj = importType.getEntityName().newInstance();
                    errMsg += assembleCorePO(sbreplace, obj, REGEX);
                    if (StringUtil.isNotEmpty(errMsg)) {
                        log.info("文件:" + filePath + "，错误:" + errMsg);
                        throw new IllegalArgumentException();
                    }
                    objList.add(obj);

                }
                log.info("文件:" + filePath + "，一共有" + (i - 1) + "行数据");
            } catch (Exception e) {
                log.error("解析文件失败！", e);
                throw new IllegalArgumentException("解析文件失败!" + errMsg);
            } finally {
                bufReader.close();
                inputStreamReader.close();
                fileInputStream.close();
            }
        }
        try {
            //批量导入
            Object serviceObj = SpringContextUtils.getBean(importType.getServiceName());
            Method method = importType.getServiceName().getMethod("initSaveBatch", Collection.class, Boolean.class);
            method.invoke(serviceObj, objList, tmepFlag);
            log.info("【" + importType.getTypeCN() + "】" + "数据导入完成, 所耗总时间:" + (System.currentTimeMillis() - start) + "ms");
        } catch (Exception e) {
            log.error("插入文件数据失败！", e);
            throw new IllegalArgumentException("插入文件数据失败!");
        }
        return true;
    }

    /*public static void main(String[] args) {
        String line = "z120001|1010000002507698806202300203|775883502|100000025076988|606779941784|印鉴相符 余额相符 对账完成|湖南创毅机械设备有限公司|中国湖南省长沙市雨花区长沙市雨花区同升街道万家丽南路二段111号颐景名苑7栋1315|网银(单一)|网银渠道|12304|202306|否|非重点";
        Field[] fields = new CaBalDtl().getClass().getDeclaredFields();//字段数

        String[] fieldVals = line.split("\\|", -1);//实际数据
        System.out.println("实际数据:" + fieldVals.length);
        System.out.println("字段数:" + fields.length);

        line.replace("|||", "Ⅲ");
        line.replace("||", "DDD");
        if (line.contains("||") || line.contains("|||")) {
            System.out.println("存在||");
        }
        for (int i = 0; i < fieldVals.length; i++) {
            String fieldVal = fieldVals[i].trim();
            Field field = fields[i + 1];//跳过默认序列号字段

            System.out.print("字段名:" + field);
            System.out.println("实际数据:" + fieldVal);
        }

        System.out.println("数据解析后，问题数据是:" + line);
        System.out.println("数据解析后，问题数据是:" + line.replace("||", "DDD"));
    }*/


    /**
     * 字符串转换成对应PO对象
     *
     * @param line  字符串
     * @param po    po对象
     * @param regex 分割符号
     * @return
     */
    private String assembleCorePO(String line, Object po, String regex) {
        String errInfo = "";
        try {
            // 栏位数组
            String[] fieldVals = line.split(regex, -1);
            // 装载po
            Field[] fields = po.getClass().getDeclaredFields();
            int num1 = fieldVals.length;//实际数据集合
            int num2 = fields.length;//字段元素集合
            if (line.contains("||") || line.contains("|||")) {
                //自动生成的实体类会生成默认序列号，这个不需要
                if (num1 > (num2 - 1)) {
                    log.info("数据解析有问题，问题数据是：{}，需要把多余的|排除", line);
                    String str = line.replace("|||", "Ⅲ");
                    str = str.replace("||", "Ⅱ");
                    log.info("数据解析后，数据是：{}", str);
                    fieldVals = str.split(regex, -1);
                }
            }

            for (int i = 0; i < fieldVals.length; i++) {
                String fieldVal = fieldVals[i].trim();
                int num = i + 1;
                //日志打印，定位问题
                if (num >= fields.length) {
                    log.info("数据解析错误，数据是：{}", line);
                }
                Field field = fields[num];//跳过默认序列号字段
                try {
                    if (!StringUtils.isEmpty(fieldVal)) {
                        fieldVal = getEnumIndx(field, fieldVal);//对有标签的字段进行枚举转换
                        String methodName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
                        if ("id".equals(field.getName())) {
                            //自动生成一个随机数做主键
//                            fieldVal = DateUtils.date2Str(new SimpleDateFormat(
//                                    "yyyyMMddHHmmssSSS")) + RandomStringUtils.randomNumeric(5) + fieldVal;
                            //由系统自动分配一个主键
                            continue;
                        }
                        if (BigDecimal.class.getName().equals(field.getType().getTypeName())) { //高精度类型
                            Method method = po.getClass().getMethod(methodName, BigDecimal.class);
                            method.invoke(po, new BigDecimal(fieldVal));
                        } else if (Integer.class.getName().equals(field.getType().getTypeName())) { //整型
                            Method method = po.getClass().getMethod(methodName, Integer.class);
                            method.invoke(po, Integer.valueOf(fieldVal));
                        } else if (Timestamp.class.getName().equals(field.getType().getTypeName())) { //时间戳
                            Method method = po.getClass().getMethod(methodName, Timestamp.class);
                            method.invoke(po, new Timestamp(DateUtils.str2Date(fieldVal, DateUtils.time_sdf).getTime()));
                        } else if (Date.class.getName().equals(field.getType().getTypeName())) { //日期类型，按照生成的格式
                            DateTimeFormat dateValidate = field.getDeclaredAnnotation(DateTimeFormat.class);
                            Method method = po.getClass().getMethod(methodName, Date.class);
                            method.invoke(po, DateUtils.str2Date(fieldVal, new SimpleDateFormat(dateValidate.pattern())));
                        } else {
                            Method method = po.getClass().getMethod(methodName, String.class);
                            method.invoke(po, fieldVal);
                        }
                    }
                } catch (Exception e) {
                    ApiModelProperty dlValidate = field.getDeclaredAnnotation(ApiModelProperty.class);
                    if (dlValidate.value() != null) {
                        errInfo += dlValidate.name() + "解析异常;";
                    } else {
                        errInfo += "第" + (i + 1) + "个栏位解析异常;";
                    }
                }
            }
        } catch (Exception e) {
            errInfo += "，数据格式错误:" + e.getMessage();
            log.error("数据格式错误！", e);
        }
        return errInfo;
    }


    /**
     * 判断是否有注解，有注解对他进行值的改写
     *
     * @param field
     * @param fieldVal
     * @return
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private String getEnumIndx(Field field, String fieldVal) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //处理被枚举标签标注的对象
        ImportEnumAnnotation enumAnnotation = field.getDeclaredAnnotation(ImportEnumAnnotation.class);

        if (enumAnnotation != null && StringUtil.isNotEmpty(enumAnnotation.enumName())) {
            String enumName = enumAnnotation.enumName();
            String defaultVal = enumAnnotation.defaultVal(); //默认值
            //反射获取枚举类
            Class<Enum> clazz = (Class<Enum>) Class.forName("com.boc.ewr.util.file.enumeration." + enumName);
            Method index = clazz.getMethod("getIndex");
            Method val = clazz.getMethod("getValue");
            //得到enum的所有实例
            Enum[] objs = clazz.getEnumConstants();
            boolean tmpFlag = false;
            for (Enum enumObj : objs) {
                if (val.invoke(enumObj).equals(fieldVal)) {
                    fieldVal = index.invoke(enumObj).toString();
                    tmpFlag = true;
                    break;
                }

            }
            //存在默认值，且没有命中枚举的情况下，赋值默认值
            if (StringUtil.isNotEmpty(defaultVal) && !tmpFlag) {
                fieldVal = defaultVal;
            }
        }
        return fieldVal;
    }


    /**
     * 批量上传成功后，记录上传文件情况
     *
     * @param showUrls
     * @param businessType
     * @return
     * @throws IOException
     */
    public void saveBatchInfo(List<String> showUrls, String businessType) throws IOException {
//        log.info("解析文件开始，文件名:" + filePath);
        List<String> md5List = new ArrayList<>();
        for (String filePath : showUrls) {
            log.info("生成MD5编码的文件：{}", filePath);
            File file = FileUtils.getFile(filePath);
            String md5str = DigestUtils.md5Hex(FileUtils.openInputStream(file));
            md5List.add(md5str);
        }
        EwrBatchuploadInfo info = new EwrBatchuploadInfo();
        //主键
        String id = DateUtils.date2Str(DateUtils.yyyymmddhhmmss) + new Random().nextInt(1000);
        info.setId(id);
        info.setBiMd5(JSON.toJSONString(md5List));
        info.setBiType(businessType);
        info.setBiName(ImportEnum.getByIndex(businessType).getTypeCN());
        info.setBiPath(JSON.toJSONString(showUrls));
        info.setCreateTime(DateUtils.getDateTime());
        // 当前登录用户
        LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        info.setCreateName(user.getUsername());
        infoService.save(info);
    }

    /**
     * 根据上传文件的类型，判断涉及这个文件的功能是否可以生成数据
     *
     * @param businessType
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public void creInfoByBatInfo(String businessType) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        //遍历枚举，筛选出哪些功能涉及
        log.info("==================遍历判断是否触发生成数据========================");
        long start = System.currentTimeMillis();
        List<ExportEnum> exportEnums = new ArrayList<>();
        ExportEnum[] exports = ExportEnum.values();
        for (ExportEnum exportEnum : exports) {
            if (exportEnum.getIndex().contains(businessType)) {
                exportEnums.add(exportEnum);
            }
        }
        log.info("遍历可能需要生成数据类型, 所耗时间:" + (System.currentTimeMillis() - start) + "ms");
        start = System.currentTimeMillis();
        //查询现有文件上传成功的历史记录,并判断文件是否完整，文件完整的业务触发数据生成，未完成的功能跳过
        //TODO 这个涉及查询历史记录，如果历史记录过大，可能影响性能
        List<EwrBatchuploadInfo> batchs = infoService.list();
        if (batchs.isEmpty() && batchs.size() == 0) {
            return;
        }
        log.info("遍历历史上传文件信息, 所耗时间:" + (System.currentTimeMillis() - start) + "ms");
        start = System.currentTimeMillis();
        //遍历符合条件的业务，开始生成数据
        for (ExportEnum exportEnum : exportEnums) {
            //获取需要判断的文件
            String[] indexs = exportEnum.getIndex().split(REGEX);
            if (isValid(indexs, batchs)) {
                //所有文件都已经上传成功，触发生成数据的方法
                Class classSerice = exportEnum.getServiceName();
                Method method = classSerice.getMethod("createInfo");
                method.invoke(SpringContextUtils.getBean(classSerice));
                log.info("功能：{}文件齐全，触发数据生成完成！", exportEnum.getTypeCN());
            } else {
                log.info("功能：{}文件不齐全！", exportEnum.getTypeCN());
            }
        }
        log.info("生成数据完成, 所耗时间:" + (System.currentTimeMillis() - start) + "ms");
    }

    /**
     * 判断所有的文件是否都已经上传成功
     *
     * @param indexs
     * @param batchInfo
     * @return
     */
    public static boolean isValid(String[] indexs, List<EwrBatchuploadInfo> batchInfo) {
        boolean validateFlag = true;
        for (String index : indexs) {
            if (!judgeExit(index, batchInfo)) {
                validateFlag = false;
            }
        }

        return validateFlag;
    }

    /**
     * 判断是否存在集合中
     *
     * @param index
     * @param batchInfo
     * @return
     */
    public static boolean judgeExit(String index, List<EwrBatchuploadInfo> batchInfo) {
        if (batchInfo.stream().filter(w -> index.equals(w.getBiType())).findAny().isPresent()) {
            return true;
        } else {
            return false;
        }
    }

}
